package lib.rpc;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.UUID;

import lib.ArgumentValidator;
import lib.messaging.NetworkAddress;

/**
 * Represents an RPC invoke request Provides marshalling as well to send/receive
 * the packet over wire
 *
 * @author abhip
 *
 */
public class RPCInvokePacket implements Serializable {
    /**
     * serialVersionUID is a universal version identifier for a Serializable
     * class. Deserialization uses this number to ensure that a loaded class
     * corresponds exactly to a serialized object. If no match is found during
     * deserialization, an InvalidClassException is thrown.
     */
    private static final long serialVersionUID = 8109776501158525307L;

    /**
     * Deserializes RPCInvokePacket from byte array
     *
     * @param data
     * @return deserialized RPCInvokePacket
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static RPCInvokePacket Deserialize(byte[] data)
            throws IOException, ClassNotFoundException
    {
        ObjectInputStream objectInputStream = new ObjectInputStream(
                new ByteArrayInputStream(data));

        Object obj = objectInputStream.readObject();
        if (obj instanceof RPCInvokePacket)
        {
            return (RPCInvokePacket) obj ;
        }

        throw new IOException(
                String.format(
                        "Could not deserialize RPCInvokePacket! Got packet of type: %s, Packet details: %s",
                        obj.getClass(),
                        obj.toString()));

    }

    private NetworkAddress clientAddress;
    private String methodName;
    private Serializable[] params;
    private UUID requestID;
    private NetworkAddress serverAddress;

    public RPCInvokePacket(
            NetworkAddress clientAddress,
            NetworkAddress serverAddress,
            UUID requestID,
            String methodName,
            Serializable[] params)
    {
        ArgumentValidator.IsNotNull("clientAddress", clientAddress);
        ArgumentValidator.IsNotNull("serverAddress", serverAddress);
        ArgumentValidator.IsNotNull("requestID", requestID);
        ArgumentValidator.IsNotNullOrEmpty("methodName", methodName);
        // params can be null, when method does not expect any arguments

        this.clientAddress = clientAddress;
        this.serverAddress = serverAddress;
        this.requestID = requestID;
        this.methodName = methodName;
        this.params = params;
    }

    /**
     * Getter for clientAddress
     *
     * @return clientAddress
     */
    public NetworkAddress ClientAddress() {
        return clientAddress;
    }

    /**
     * Getter for methodName
     *
     * @return methodName
     */
    public String MethodName() {
        return methodName;
    }

    /**
     * Getter for params
     *
     * @return params
     */
    public Serializable[] Params() {
        return params;
    }

    /**
     * Getter for sequenceID
     *
     * @return sequenceID
     */
    public UUID RequestID() {
        return requestID;
    }

    /**
     * Serializes RPCInvokePacket to byte array
     *
     * @return serialized byte array
     * @throws IOException
     */
    public byte[] Serialize() throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(
                byteArrayOutputStream);
        objectOutputStream.writeObject(this);
        objectOutputStream.flush();
        return byteArrayOutputStream.toByteArray();
    }

    /**
     * Getter for serverAddress
     *
     * @return serverAddress
     */
    public NetworkAddress ServerAddress() {
        return serverAddress;
    }

    /**
     * Returns string representation of RPCInvokePacket
     *
     * @return
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(
                String.format(
                        "client: %s:%d, server: %s:%d, requestID: %s, methodName: %s, params: ",
                        clientAddress.Name(),
                        clientAddress.Port(),
                        serverAddress.Name(),
                        ServerAddress().Port(),
                        requestID,
                        methodName));

        if (params != null) {
            for (int i = 0; i < params.length; ++i) {
                sb.append(params[i]);
                sb.append(" ");
            }
        }

        return sb.toString();
    }
}
